#include "ZigbusMessage.h"

#include <QDebug>
#include "ZigbusOrder.h"
#include "ZigbusAddressNeed.h"
#include "ZigbusType.h"
#include "ZigbusNumericIniState.h"
#include "ZigbusIO.h"
#include "ZigbusMeteoType.h"
#include "ZigbusAddressingType.h"
#include "ZigbusTimeItem.h"
#include "ZigbusOrderDirection.h"
#include "ZigbusTemperatureItem.h"
#include "ZigbusHygroItem.h"
#include "ZigbusServoPosition.h"
#include "ZigbusPwmValue.h"
#include "ZigbusTieRevertItem.h"
#include "ZigbusTieActivItem.h"
#include "ZigbusIsAnalogicItem.h"

ZigbusMessage::ZigbusMessage()
    : networkId(-1), moduleId(-1), order(-1)
{
}

ZigbusMessage::ZigbusMessage(const ZigbusNetworkId &networkId, const ZigbusModuleId &moduleId,
                             const ZigbusOrder& order)
    : networkId(networkId), moduleId(moduleId), order(order)
{
}

ZigbusMessage ZigbusMessage::createMessage(const QString &str, bool* ok)
{
    qDebug() << "[zigbus] generating zigbus message...";
    if(str.isEmpty()) {
        qDebug() << "[zigbus] message is empty";
        if(ok) *ok=false;
        return ZigbusMessage();
    }
    ZigbusMessage resultMessage;
    bool conversionOK = false;
    QString strNetworkId = str.mid(0, 2);
    int inetwork = strNetworkId.toInt(&conversionOK);
    if(!conversionOK) {
        qDebug() << "[zigbus] invalid network id";
        if(ok) *ok=false;
        return ZigbusMessage();
    }
    resultMessage.setNetworkId(ZigbusNetworkId(inetwork));
    qDebug() << "[zigbus] networkId \tdone";
    QString strModuleId = str.mid(2, 4);
    int imodule = strModuleId.toInt(&conversionOK, 16);
    if(!conversionOK) {
        qDebug() << "[zigbus] invalid module id";
        if(ok) *ok=false;
        return ZigbusMessage();
    }
    resultMessage.setModuleId(ZigbusModuleId(imodule));
    qDebug() << "[zigbus] moduleId \tdone";
    QString trame = str.mid(6);
    int value = trame.toInt(&conversionOK, 16);
    if(!conversionOK) {
        if(ok) *ok=false;
        return ZigbusMessage();
    }
    int iorder = (value & 0x1F);
    ZigbusOrder order(iorder);
    if(!order.isValid()) {
        qDebug() << "[zigbus] invalid order";
        if(ok) *ok = false;
        return ZigbusMessage();
    }
    resultMessage.setOrder(order);
    qDebug() << "[zigbus] order \t\tdone";
    // Module init message without arguments
    if(value == 0x1F) {
        if(ok) *ok = true;
        return resultMessage;
    }
    value = (value >> ORDER_SIZE);
    if(order == ZigbusOrder::INIT()) {
        int ineed = (value & 0x01);
        ZigbusAddressNeed addrNeeded(ineed);
        resultMessage.addAddrNeeded(addrNeeded);
        qDebug() << "INIT MESSAGE";
    }
    else if(order == ZigbusOrder::CONFIG()) {
        qDebug() << "CONFIG MESSAGE";
        //we now need a type with the 5 following bits
        int itype = (value & 0x1F);
        value = (value >> TYPE_SIZE);
        ZigbusType type(itype);
        resultMessage.addType(type);

        if(type == ZigbusType::NUMERIC_OUTPUT()) {
            int istate = (value & 0x01);
            value = (value >> NUMERIC_INI_STATE_SIZE);
            ZigbusNumericIniState state(istate);
            resultMessage.addNumericIniState(state);
        }
        else if(type == ZigbusType::NUMERIC_INPUT()) {
            //nothing to do
        }
        else if(type == ZigbusType::SERVO()) {
            int ipui = (value & 0x7F);
            value = (value >> IO_SIZE);
            ZigbusIO pui(ipui);
            resultMessage.addIO(pui);
        }
        else if(type == ZigbusType::PWM_OUTPUT()) {
            int istate = (value & 0x01);
            value = (value >> NUMERIC_INI_STATE_SIZE);
            ZigbusNumericIniState state(istate);
            resultMessage.addNumericIniState(state);
        }
        else if(type == ZigbusType::METEO()) {
            int iref = (value & 0x07);
            value = (value >> METEO_TYPE_SIZE);
            ZigbusMeteoType meteoType(iref);
            resultMessage.addMeteoType(meteoType);
        }
        else if(type == ZigbusType::HEATER()) {
            int ialter = (value & 0x7F);
            value = (value >> IO_SIZE);
            ZigbusIO alter(ialter);
            resultMessage.addIO(alter);
        }

        //dernier item en général l'id pin principal
        int imainpin = (value & 0x7F); //sur 7 bits
        value = (value >> IO_SIZE);
        ZigbusIO mainPin(imainpin);
        resultMessage.addIO(mainPin);

    }
    else if(order == ZigbusOrder::ON() || order == ZigbusOrder::OFF() || order == ZigbusOrder::TOGGLE()) {
        int isgroup = (value & 0x01);
        value = (value >> ADDRESSING_TYPE_SIZE);
        ZigbusAddressingType addrType(isgroup);
        resultMessage.addAddressingType(addrType);

        int imainpin = (value & 0x7F); //sur 7 bits
        value = (value >> IO_SIZE);
        ZigbusIO mainPin(imainpin);
        resultMessage.addIO(mainPin);

        int itime = (value & 0x1FFF); // sur 13 bits (value = 12 / unit = 1)
        value = (value >> TIME_SIZE);
        ZigbusTimeItem timeItem(itime);
        resultMessage.addTimeItem(timeItem);
    }
    else if(order == ZigbusOrder::SERVO()) {
        int iservo_position = (value & 0xFF);
        value = (value >> SERVO_POSITION_SIZE);
        ZigbusServoPosition servoPositionItem(iservo_position);
        resultMessage.addServoPosition(servoPositionItem);

        int icmd = (value & 0x7F); //sur 7 bits
        value = (value >> IO_SIZE);
        ZigbusIO cmdItem(icmd);
        resultMessage.addIO(cmdItem);

        int ipui = (value & 0x7F); //sur 7 bits
        value = (value >> IO_SIZE);
        ZigbusIO puiItem(ipui);
        resultMessage.addIO(puiItem);

    }
    else if(order == ZigbusOrder::PWM()) {
        int imainpin = (value & 0x7F); //sur 7 bits
        value = (value >> IO_SIZE);
        ZigbusIO mainPin(imainpin);
        resultMessage.addIO(mainPin);

        int ipwm_value = (value & 0x3FF);
        value = (value >> PWM_VALUE_SIZE);
        ZigbusPwmValue pwmValueItem(ipwm_value);
        resultMessage.addPwmValue(pwmValueItem);
    }
    else if(order == ZigbusOrder::METEO()) {
        int imeteo_type = (value & 0x07);
        value = (value >> METEO_TYPE_SIZE);
        ZigbusMeteoType meteoType(imeteo_type);
        resultMessage.addMeteoType(meteoType);

        int iorder_direction = (value & 0x01);
        value = (value >> ORDER_DIRECTION_SIZE);
        ZigbusOrderDirection direction(iorder_direction);
        resultMessage.addOrderDirection(direction);

        int imainpin = (value & 0x7F); //sur 7 bits
        value = (value >> IO_SIZE);
        ZigbusIO mainPin(imainpin);
        resultMessage.addIO(mainPin);

        if(direction == ZigbusOrderDirection::RESPONSE()) {
            int itemp = (value & 0x3F);
            value = (value >> TEMPERATURE_ITEM_SIZE);
            ZigbusTemperatureItem tempItem(itemp);
            resultMessage.addTemperature(tempItem);

            int ihygro = (value & 0x7F);
            value = (value >> HYGRO_ITEM_SIZE);
            ZigbusHygroItem hygroItem(ihygro);
            resultMessage.addHygro(hygroItem);
        }
    }
    /* A TESTER */
    else if(order == ZigbusOrder::TIE()) {
        //OUTPUT  REFERNCE INVERS ACTIV   TIE
        //0000000  0000000   1     1     01110
        int iactiv = (value & 0x01);
        value = (value >> TIE_ACTIV_ITEM_SIZE);
        ZigbusTieActivItem activItem(iactiv);
        resultMessage.addTieActiv(activItem);

        int irevert = (value & 0x01);
        value = (value >> TIE_REVERT_ITEM_SIZE);
        ZigbusTieRevertItem revertItem(irevert);
        resultMessage.addTieRevert(revertItem);

        int iref = (value & 0x7F);
        value = (value >> IO_SIZE);
        ZigbusIO refItem(iref);
        resultMessage.addIO(refItem);

        int ioutput = (value & 0x7F);
        value = (value >> IO_SIZE);
        ZigbusIO ioItem(ioutput);
        resultMessage.addIO(ioItem);
    }
    /* A TESTER */
    else if(order == ZigbusOrder::REBOOT()) {
        /* nothing to do */
    }
    else if(order == ZigbusOrder::STATE()) {
        // IO(7b)+NUM/ANA(1b)+STATUS(5b)
        int inum = (value & 0x01);
        value = (value >> IS_ANALOGIC_ITEM_SIZE);
        ZigbusIsAnalogicItem anaItem(inum);
        resultMessage.addIsAnalogItem(anaItem);

        int iIO = (value & 0x7F);
        value = (value >> IO_SIZE);
        ZigbusIO ioItem(iIO);
        resultMessage.addIO(ioItem);
    }
    if(ok) *ok = true;
    return resultMessage;
}

ZigbusMessage::ZigbusMessage(const ZigbusMessage &other)
{
    this->moduleId = other.moduleId;
    this->networkId = other.networkId;
    this->order = other.order;
    for(int i = 0; i < other.itemList.count(); i++) {
        itemList.append(ZigbusItemFactory::duplicate(other.itemPtrAt(i)));
    }
}

ZigbusMessage::~ZigbusMessage()
{
    clearArgList();
}

ZigbusMessage& ZigbusMessage::operator =(const ZigbusMessage& other)
{
    this->moduleId = other.moduleId;
    this->networkId = other.networkId;
    this->order = other.order;
    clearArgList();
    for(int i = 0; i < other.itemList.count(); i++) {
        itemList.append(ZigbusItemFactory::duplicate(other.itemPtrAt(i)));
    }
    return *this;
}

bool ZigbusMessage::equals(const ZigbusMessage &other) const
{
    if(this->moduleId != other.moduleId) return false;
    if(this->networkId != other.networkId) return false;
    if(this->itemList != other.itemList) return false;
    return true;
}

bool operator==(const ZigbusMessage& a, const ZigbusMessage& b)
{
    return a.equals(b);
}

QString ZigbusMessage::trame() const
{
    QString rslt = networkId.trame();
    rslt += moduleId.trame();
    int tmpValue = toInt();
    rslt += QString::number(tmpValue, 16);
    return rslt.toUpper();
}

int ZigbusMessage::toInt() const
{
    int tmpValue = 0;
    for(int i = itemList.count()-1;  i >= 0; i--) {
        tmpValue = tmpValue << itemList.at(i)->getSize();
        tmpValue |= itemList.at(i)->getValue();
    }
    tmpValue = tmpValue << order.getSize();
    tmpValue |= order.getValue();
    return tmpValue;
}

QString ZigbusMessage::toString() const
{
    QString rslt = "";
    rslt += "network(dec) = "+QString::number(networkId.getValue(), 10)+"\n";
    rslt += "module(oct) = "+QString::number(moduleId.getValue(), 8)+"\n";
    rslt += "order = "+order.text()+"("+QString::number(order.getValue())+")\n";
    for(int i = 0; i < this->itemList.count(); i++) {
        rslt += itemAt(i).type()+" = "+itemAt(i).text()+"\n";
    }
    return rslt;
}

void ZigbusMessage::clearArgList()
{
    while(!itemList.isEmpty()) {
        ZigbusItem* item = itemList.takeFirst();
        if(item) delete item;
    }
}

const ZigbusItem& ZigbusMessage::itemAt(int i) const
{
    return *(itemList.at(i));
}

ZigbusItem* ZigbusMessage::itemPtrAt(int i) const
{
    return itemList[i];
}

void ZigbusMessage::addOrder(const ZigbusOrder &order)
{
    itemList.append(new ZigbusOrder(order));
}

void ZigbusMessage::addAddrNeeded(const ZigbusAddressNeed &need)
{
    itemList.append(new ZigbusAddressNeed(need));
}

void ZigbusMessage::addType(const ZigbusType &type)
{
    itemList.append(new ZigbusType(type));
}

void ZigbusMessage::addNumericIniState(const ZigbusNumericIniState &iniState)
{
    itemList.append(new ZigbusNumericIniState(iniState));
}

void ZigbusMessage::addIO(const ZigbusIO &io)
{
    itemList.append(new ZigbusIO(io));
}

void ZigbusMessage::addMeteoType(const ZigbusMeteoType &meteoType)
{
    itemList.append(new ZigbusMeteoType(meteoType));
}

void ZigbusMessage::addAddressingType(const ZigbusAddressingType &type)
{
    itemList.append(new ZigbusAddressingType(type));
}

void ZigbusMessage::addTimeItem(const ZigbusTimeItem &time)
{
    itemList.append(new ZigbusTimeItem(time));
}

void ZigbusMessage::addServoPosition(const ZigbusServoPosition &position)
{
    itemList.append(new ZigbusServoPosition(position));
}

void ZigbusMessage::addPwmValue(const ZigbusPwmValue &value)
{
    itemList.append(new ZigbusPwmValue(value));
}

void ZigbusMessage::addTemperature(const ZigbusTemperatureItem &temp)
{
    itemList.append(new ZigbusTemperatureItem(temp));
}

void ZigbusMessage::addHygro(const ZigbusHygroItem &hygro)
{
    itemList.append(new ZigbusHygroItem(hygro));
}

void ZigbusMessage::addTieActiv(const ZigbusTieActivItem &activ)
{
    itemList.append(new ZigbusTieActivItem(activ));
}

void ZigbusMessage::addOrderDirection(const ZigbusOrderDirection &direction)
{
    itemList.append(new ZigbusOrderDirection(direction));
}

void ZigbusMessage::addTieRevert(const ZigbusTieRevertItem &revert)
{
    itemList.append(new ZigbusTieRevertItem(revert));
}

void ZigbusMessage::addIsAnalogItem(const ZigbusIsAnalogicItem &isAnalog)
{
    itemList.append(new ZigbusIsAnalogicItem(isAnalog));
}

void ZigbusMessage::addAnalogicRef(const ZigbusAnalogicReference &ref)
{
    itemList.append(new ZigbusAnalogicReference(ref));
}

bool ZigbusMessage::isValid() const
{
    if(!getModuleId().isValid()
            || !getNetworkId().isValid()
            || !getOrder().isValid())
        return false;
    for(int i = 0; i < itemList.count(); i++)
        if(!itemList.at(i)->isValid()) return false;
    return true;
}

QString ZigbusMessage::fullAddress() const
{
    QString rslt = "";
    QString tmp;
    tmp = QString::number(this->getNetworkId().getValue(), 10);
    while(tmp.count() < 2)
        tmp = "0"+tmp;
    rslt += tmp;
    tmp = QString::number(getModuleId().getValue(), 8);
    while(tmp.count() < 4)
        tmp = "0"+tmp;
    rslt += tmp;
    return rslt;
}
